/***************************************************************************
 *   Copyright (C) 2008 by Łukasz Spintzyk   *
 *   lukasz.spintzyk@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "t6963.h"
#include "digital_oscilloscope.h"

uint16_t textHome=0x1800;
uint16_t graphHome=0x0000;
char char81[8]={0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01};
char char82[8]={0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02};
char char83[8]={0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04};
char char84[8]={0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08};
char char85[8]={0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10};
char char86[8]={0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20};
char temp[8]={0};

void addCharacters(){
        init_CGRAM(char81,0x2c00);
        init_CGRAM(char82,0x2c08);
        init_CGRAM(char83,0x2c10);
        init_CGRAM(char84,0x2c18);
        init_CGRAM(char85,0x2c20);
        init_CGRAM(char86,0x2c28);
        uint8_t x=0;
        uint16_t address=0x2c30;
        for (x=0;x<8;x++){
            temp[7-x]=0xFF;
            init_CGRAM(temp,address);
            temp[7-x]=0x00;
            address+=8;
        }
        
}

void display_text(const char *buffer,uint8_t x,uint8_t y){
uint16_t pos;
uint8_t col=40;
    pos=textHome+(y*col)+x;
    lcd_write_2cmd((uint8_t)pos,(uint8_t)(pos>>8),SETADDRESSPTR);//address pointer set
        while((*buffer)!='\0'){
            lcd_write_1cmd((*buffer++)-0x20,SETWRANDINC);
	}
}

void display_text_PGM(PGM_P buffer,uint8_t x,uint8_t y){
    uint16_t pos;
    uint8_t col=40;
    pos=textHome+(y*col)+x;
    lcd_write_2cmd((uint8_t)pos,(uint8_t)(pos>>8),SETADDRESSPTR);//address pointer set
    while(pgm_read_byte(buffer)!='\0'){
        lcd_write_1cmd(pgm_read_byte(buffer++)-0x20,SETWRANDINC);
    }
}


/*! Displays ram_char character from cgRam at position x,y
 */
void display_char(char ram_char,uint8_t x,uint8_t y){
uint16_t pos;
//! \todo replae with macro
uint8_t col=40;
    pos=textHome+(y*col)+x;
    lcd_write_2cmd((uint8_t)pos,(uint8_t)(pos>>8),SETADDRESSPTR);//address pointer set
    lcd_write_1cmd(ram_char,SETWRANDINC);//wpisz do pamieci TA kod wczesniej zdefiniowanego znaku 
}

/*! Writes custom character code to CG-RAM area
 * \param character -pointer to table of 8 bytes code
 * \param addr - addres of character in cg ram
 */
void init_CGRAM(char* character,uint16_t addr){
uint8_t i;
    lcd_write_2cmd((uint8_t)addr,(uint8_t)(addr>>8),SETADDRESSPTR);

    lcd_write_0cmd(SETAUTOWRITE);//data auto write
    for(i=0;i<8;i++){
	while((lcd_read_status()&8)!=8);
	lcd_write_data(*character++);
    }
    lcd_write_0cmd(SETAUTORESET);//reset auto write
}



/*! Initialization procedure for LCD
 */
void lcd_init(uint16_t ptextHome, uint16_t pgraphHome,uint8_t textArea,uint8_t graphArea){
    lcd_hwd_init();
    
    textHome=ptextHome;
    graphHome=pgraphHome;
    
    lcd_write_0cmd(SETMODE | ORMODE | INTERNALCGROM);   //set mode
    lcd_write_2cmd((uint8_t)pgraphHome,(uint8_t)(pgraphHome>>8),SET_GH_ADDR);

    lcd_write_2cmd(graphArea,0,SET_GA);
    

    lcd_write_2cmd((uint8_t)ptextHome,(uint8_t)(ptextHome>>8),SET_TH_ADDR);
    lcd_write_2cmd(textArea,0,SET_TA);
    
    lcd_write_2cmd(5,0,SETOFFSETREG);//offset text VRAM = 0x2800

    
    
    init_text_ram(ptextHome);
    init_graph_ram(0,pgraphHome);
    addCharacters();
    lcd_write_0cmd(SETDISPLAYMODE | TEXTONGRAPHICON);//display ON
}

/*! Initialization of LCD TEXT RAM AREA with zero
 * \param textArea - begin address(16bit) of text area in VRAM of LCD
 */
void init_text_ram(uint16_t textArea){
    uint16_t i;
    lcd_write_2cmd((uint8_t)textArea,(uint8_t)(textArea>>8),SETADDRESSPTR);//set pointer address on begin of TextHome
    lcd_write_0cmd(SETAUTOWRITE);
    
    //Display can handle max 40 characters per row, and row count is 8 
    for(i=0;i<8*40;i++){
	while((lcd_read_status()&8)!=8);//wait for lcd_drive
	lcd_write_data(0);
    }
    lcd_write_0cmd(SETAUTORESET);//reset auto write
}

/*! Initialization of LCD GRAPHICS RAM AREA with fill patern
 * \param fill value wich fill the memory of LCD
 * \param graphArea - begin address(16bit) of text area in VRAM of LCD
 */
void init_graph_ram(char fill,uint16_t graphArea){
    uint16_t i;
    lcd_write_2cmd((uint8_t)graphArea,(uint8_t)(graphArea>>8),SETADDRESSPTR);
    lcd_write_0cmd(SETAUTOWRITE);
    
    for(i=0;i<2560;i++){
        while((lcd_read_status()&8)!=8);
	lcd_write_data(fill);
    }
    lcd_write_0cmd(SETAUTORESET);//reset auto write
}

/*! Send command without argument
 */
void lcd_write_0cmd(unsigned char cmd){
    while((lcd_read_status()&3)!=3) ;//wait for lcd drive
    lcd_write_cmd(cmd);//send command
}
/*! Send command with one argument
 */
void lcd_write_1cmd(unsigned char arg, unsigned char cmd){
    while((lcd_read_status()&3)!=3);//wait for lcd drive
    lcd_write_data(arg);//send arg
    
    while((lcd_read_status()&3)!=3);
    lcd_write_cmd(cmd);//send command
}

/*! Send command with two arguments
 * \param arg1 Argument nr 1
 * \param arg2 Argument nr2
 * \param cmd Command code
 */
void lcd_write_2cmd(unsigned char arg1, unsigned char arg2, unsigned char cmd){
    while((lcd_read_status()&3)!=3);//wait for lcd drive
    
    lcd_write_data(arg1);//send arg1

    while((lcd_read_status()&3)!=3);
    lcd_write_data(arg2);//send arg2
	
    while((lcd_read_status()&3)!=3);
    lcd_write_cmd(cmd);//send command
}


/*! Initialization of lcd controling lines and reset */
void lcd_hwd_init(void){
    //! \todo How long delay ?
    _delay_ms(10.0);

    //! Turn on pull-up resistors so after seting pin as output they will give high state
    //! it is necessary because PORTx default value is 0
    PORT(LCD_CONTROL)|=(1<<LCD_WR)|(1<<LCD_RD)|(1<<LCD_CS)|(1<<LCD_CD)|(1<<LCD_RST);
    
    //! \warning attention for -VEE of driving lcd voltage.
    PORT(_VEE_CUT_PORT)|=(1<<_VEE_CUT_PIN); //set VEE_CUT_PIN =1(high voltage)
    DDR(_VEE_CUT_PORT)|=(1<<_VEE_CUT_PIN);  //set VEE_CUT_PIN as output, -VEE is cuted
	
    //!Set control lines as output
    DDR(LCD_CONTROL)|=(1<<LCD_WR)|(1<<LCD_RD)|(1<<LCD_CS)|(1<<LCD_CD)|(1<<LCD_RST);
    delay();
    
    _delay_ms(100.0);
    PORT(LCD_CONTROL)&=~(1<<LCD_RST);	//reset lcd
    //! \todo How long delay ?
    _delay_ms(100.0);
    PORT(LCD_CONTROL)|=(1<<LCD_RST);	//reset off
    _delay_ms(100.0);
    PORT(_VEE_CUT_PORT)&=~(1<<_VEE_CUT_PIN);//Turn on -VEE
    
}
/*! Write data to lcd_drive
 */
void lcd_write_data(unsigned char data){
    PORT(LCD_CONTROL)&=~(1<<LCD_CD);//CD=0- write data
    PORT(LCD_CONTROL)|=(1<<LCD_RD);
    delay();
    wr_lcd_bus(data);
}
/*! WRITE COMMAND FOR LCD_drive */
void lcd_write_cmd(unsigned char cmd){
    PORT(LCD_CONTROL)|=(1<<LCD_CD);//CD=1  means CODE 
    delay();
    PORT(LCD_CONTROL)|=(1<<LCD_RD);
    delay();//! \todo are delay realy necessary if we invoke function?
    wr_lcd_bus(cmd);//send command on bus
}


/*! Read data from data bus
 * \return value read from databus
 */
unsigned char lcd_read_data(void){
    //!  \todo do some cleanup with delays, remove unnecesary and make them shorter.
    PORT(LCD_DATA)&=~(1<<LCD_CD);
    delay();
    PORT(LCD_DATA)|=(1<<LCD_WR);
    delay();
    
    return(rd_lcd_bus());
}

/*! Read status register from lcd drive.
 * line LCD_CD must set high
 * \return value of status register of LCD
 */
uint8_t lcd_read_status(void){
    PORT(LCD_DATA)|=(1<<LCD_CD);
    delay();
    PORT(LCD_DATA)|=(1<<LCD_WR);
    uint8_t i=0;
    for (i=0;i<10;i++)    delay();
    //! \todo \warning See what is wrong with status read, and why it takes so long
    return 0xFF;
    //return(rd_lcd_bus());
}

/*! Read data from lcd bus
 */
uint8_t rd_lcd_bus(void){
    uint8_t data;
    DDR(LCD_DATA)=0x00;     //set port as input
    PORT(LCD_CONTROL)&=~(1<LCD_CS);     //CE=0;
   delay();
    
    PORT(LCD_CONTROL)&=~(1<<LCD_RD);    //RD=0
    delay();
    delay();
    
    data=PIN(LCD_DATA);//read
    
    PORT(LCD_CONTROL)|=(1<LCD_RD);     //CE=1;
    delay();
    
    PORT(LCD_CONTROL)|=(1<<LCD_CS);    //RD=1;
    delay();
    
    return(data);
}
/*! Send data on bus.
 * \param data -8-bit value which will be send to LCD
 */
void wr_lcd_bus(uint8_t data){
    //! \todo Schould i block here interrupts ? | probably not because of any interrupt procedure will restore DDRx registers
    DDR(LCD_DATA)=0xFF;
    //! \todo necessary delay?
    delay();
    PORT(LCD_DATA)=data;	
    delay();
    PORT(LCD_CONTROL)&=~(1<<LCD_CS);    //CE=0
    delay();
    PORT(LCD_CONTROL)&=~(1<<LCD_WR);    //WR=0
    delay();
    delay();
    delay();

    PORT(LCD_CONTROL)|=(1<<LCD_WR);    //WR=1
    PORT(LCD_CONTROL)|=(1<<LCD_CS);    //CE=1
}

void delay (void){ 
  _delay_loop_1(10);
}
